/* CsJni - .NET / Java Bridge
 * 
 * Copyright 2010 Robert E. Burke Licensed under the Apache License, Version 
 * 2.0 (the "License"); you may not use this file except in compliance with 
 * the License. You may obtain a copy of the License at 
 * http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable 
 * law or agreed to in writing, software distributed under the License is 
 * distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 
 * KIND, either express or implied. See the License for the specific language 
 * governing permissions and limitations under the License. 
*/

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Reflection;

namespace CsJni
{
    [StructLayout(LayoutKind.Sequential)]
    internal struct JniNativeMethod : IDisposable
    {
        public IntPtr name;
        public IntPtr signature;
        public IntPtr fnPtr;

        public JniNativeMethod(string name, string signature, IntPtr functionPointer)
        {
            this.name = Marshal.StringToHGlobalAnsi(name);
            this.signature = Marshal.StringToHGlobalAnsi(signature);
            this.fnPtr = functionPointer;
        }

        #region IDisposable Members

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion IDisposable Members

        private void Dispose(bool disposing)
        {
            if (!disposing && !Environment.HasShutdownStarted)
            {
                throw new JniException(JniResourceManager.Instance.GetString("invalidDisposableFinalizer"));
            }
            if (name != IntPtr.Zero)
            {
                Marshal.FreeHGlobal(name);
                name = IntPtr.Zero;
            }
            if (signature != IntPtr.Zero)
            {
                Marshal.FreeHGlobal(signature);
                signature = IntPtr.Zero;
            }
        }
    }

    public sealed class JniNativeMethodHolder
    {
        private List<JniNativeMethodEntry> mEntries = new List<JniNativeMethodEntry>();

        // The reason we have the JniNativeMethodEntry class is because 
        // we need to pass an array of JniNativeMethod structs to JniEnv.RegisterNatives, 
        // and the JniNativeMethod struct contains strings, which are not 
        // blittable, thus they cannot marshal.  To make the struct blittable, we 
        // declare the string types as IntPtr, and we create the strings on the heap 
        // (another consideration is that the strings are Ansi, not Unicode).  Since 
        // the strings are on the heap, they must be freed, thus the FreeMemory method 
        // on JniNativeMethod.  To keep the caller of JniEnv.RegisterNatives from having 
        // to free these strings, we temporarily store the string data here in 
        // the JniNativeMethodEntry inner class.  The JniEnv.RegisterNatives, 
        // then calls ToJniNativeMethodArray, which returns the array with the allocated 
        // strings, then after it makes the JNI call, it calls FreeArrayMemory to free the 
        // strings.  Thus, the memory management detail is hidden from the developer 
        // who just wants to use JniEnv.RegisterNatives.

        private class JniNativeMethodEntry
        {
            public string name;
            public string signature;
            public Delegate functionDelegate;
            public JniNativeMethodEntry(string name, string signature, Delegate functionDelegate)
            {
                this.name = name;
                this.signature = signature;
                this.functionDelegate = functionDelegate;
            }
        }

        public JniNativeMethodHolder()
        {
        }

        public void PopulateFromType(Type counterpartType)
        {
            MethodInfo[] methods = counterpartType.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public);
            for (int i = 0; i < methods.Length; i++)
            {
                MethodInfo method = methods[i];
                object[] attributes = method.GetCustomAttributes(typeof(NativeMethodInfoAttribute), false);
                if (attributes.Length == 1)
                {
                    NativeMethodInfoAttribute nmi = attributes[0] as NativeMethodInfoAttribute;
                    this.Add(nmi.Name, nmi.Signature, Delegate.CreateDelegate(nmi.DelegateType, method));
                }
            }
        }

        public void Add(string name, string signature, Delegate functionDelegate)
        {
            mEntries.Add(new JniNativeMethodEntry(name, signature, functionDelegate));
        }

        public int Count
        {
            get { return mEntries.Count; }
        }

        internal JniNativeMethod[] ToJniNativeMethodArray(out DelegatePin[] pins)
        {
            JniNativeMethod[] outArray = new JniNativeMethod[mEntries.Count];
            pins = new DelegatePin[0];
            List<DelegatePin> tempPins = new List<DelegatePin>();
            for (int i = 0; i < mEntries.Count; i++)
            {
                JniNativeMethodEntry entry = (JniNativeMethodEntry)mEntries[i];
                DelegatePin pin = new DelegatePin(entry.functionDelegate, entry.name+":"+entry.signature);
                outArray[i] = new JniNativeMethod(entry.name, entry.signature, pin.ToFunctionPointer());
                tempPins.Add(pin);
            }
            pins = tempPins.ToArray();
            return outArray;
        }

        internal static void FreeArrayMemory(JniNativeMethod[] inArray)
        {
            foreach (JniNativeMethod method in inArray)
            {
                method.Dispose();
            }
        }
    }
}
